70. 内嵌servlet容器

70.1 为应用添加Servlet,Filter或Listener

这里有两种方式可以为应用添加ServletFilterServletContextListener和其他Servlet支持的特定listeners。你既可以为它们提供Spring beans,也可以为Servlet组件启用扫描(package scan)。

70.1.1 使用Spring bean添加Servlet, Filter或Listener

想要添加ServletFilter或Servlet*Listener,你只需要为它提供一个@Bean定义,这种方式很适合注入配置或依赖。不过,需要注意的是它们不会导致其他很多beans的热初始化,因为它们需要在应用生命周期的早期进行安装(让它依赖DataSource或JPA配置不是好主意),你可以通过懒加载突破该限制(在第一次使用时才初始化)。

对于FiltersServlets,你可以通过FilterRegistrationBeanServletRegistrationBean添加映射和初始化参数。

在一个filter注册时,如果没指定dispatcherType,它将匹配FORWARDINCLUDEREQUEST。如果启用异步,它也将匹配ASYNC。如果迁移web.xml中没有dispatcher元素的filter,你需要自己指定一个dispatcherType

@Bean
public FilterRegistrationBean myFilterRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setDispatcherTypes(DispatcherType.REQUEST);
    ....

    return registration;
}

禁止Servlet或Filter的注册

如上所述,任何ServletFilter beans都将自动注册到servlet容器。不过,为特定的FilterServlet bean创建一个registration,并将它标记为disabled,可以禁用该filter或servlet。例如:

@Bean
public FilterRegistrationBean registration(MyFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
}

70.1.2 使用classpath扫描添加Servlets, Filters和Listeners

通过把@ServletComponentScan注解到一个@Configuration类并指定包含要注册组件的package(s),可以将@WebServlet@WebFilter@WebListener注解的类自动注册到内嵌servlet容器。默认情况下,@ServletComponentScan将从被注解类的package开始扫描。

70.2 改变HTTP端口

在一个单独的应用中,主HTTP端口默认为8080,不过可以使用server.port设置(比如,在application.properties中或作为系统属性)。由于Environment值的宽松绑定,你也可以使用SERVER_PORT(比如,作为OS环境变量)。

想要创建WebApplicationContext但完全关闭HTTP端点,你可以设置server.port=-1(测试时可能有用)。具体详情可查看'Spring Boot特性'章节的Section 27.3.4, “Customizing embedded servlet containers”,或ServerProperties源码。

70.3 使用随机未分配的HTTP端口

想扫描获取一个未使用的端口(使用操作系统本地端口以防冲突)可以设置server.port=0

70.4 发现运行时的HTTP端口

你可以通过日志输出或它的EmbeddedServletContainerEmbeddedWebApplicationContext获取服务器正在运行的端口。获取和确认服务器已经初始化的最好方式是添加一个ApplicationListener<EmbeddedServletContainerInitializedEvent>类型的@Bean,然后当事件发布时将容器pull出来。

使用@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)进行测试时,你可以通过@LocalServerPort注解将实际端口注入到字段中,例如:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyWebIntegrationTests {

    @Autowired
    EmbeddedWebApplicationContext server;

    @LocalServerPort
    int port;

    // ...

}

@LocalServerPort@Value("${local.server.port}")的元数据,在常规的应用中不要尝试注入端口。正如我们看到的,该值只会在容器初始化后设置。相对于测试,应用代码回调处理的会更早(例如在该值实际可用之前)。

70.5 配置SSL

你可以以声明方式配置SSL,一般通过在application.propertiesapplication.yml设置各种各样的server.ssl.*属性,例如:

server.port = 8443
server.ssl.key-store = classpath:keystore.jks
server.ssl.key-store-password = secret
server.ssl.key-password = another-secret

查看Ssl获取所有支持的配置。

使用类似于以上示例的配置意味着该应用将不支持端口为8080的普通HTTP连接。Spring Boot不支持通过application.properties同时配置HTTP连接器和HTTPS连接器。如果你两个都想要,那就需要以编程的方式配置它们中的一个。推荐使用application.properties配置HTTPS,因为HTTP连接器是两个中最容易以编程方式进行配置的,查看spring-boot-sample-tomcat-multi-connectors可获取示例项目。

70.6 配置访问日志

通过相应的命令空间可以为Tomcat和Undertow配置访问日志,例如下面是为Tomcat配置的一个自定义模式的访问日志:

server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)

日志默认路径为tomcat基础路径下的logs目录,该dir默认是个临时目录,所以你可能想改变Tomcat的base目录或为日志指定绝对路径。上述示例中,你可以在相对于应用工作目录的my-tomcat/logs访问到日志。

Undertow的访问日志配置方式类似:

server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a "%r" %s (%D ms)

日志存储在相对于应用工作目录的logs目录下,可以通过server.undertow.accesslog.directory自定义。

70.7 在前端代理服务器后使用

你的应用可能需要发送302跳转或使用指向自己的绝对路径渲染内容。当在代理服务器后面运行时,调用者需要的是代理服务器链接而不是部署应用的实际物理机器地址,通常的解决方式是代理服务器将前端地址放到headers并告诉后端服务器如何拼装链接。

如果代理添加约定的X-Forwarded-ForX-Forwarded-Proto headers(大多数都是开箱即用的),只要将application.properties中的server.use-forward-headers设置为true,绝对链接就能正确的渲染。

如果应用运行在Cloud Foundry或Heroku,server.use-forward-headers属性没指定的话默认为true,其他实例默认为false

70.7.1 自定义Tomcat代理配置

如果使用的是Tomcat,你可以配置用于传输"forwarded"信息的headers名:

server.tomcat.remote-ip-header=x-your-remote-ip-header
server.tomcat.protocol-header=x-your-protocol-header

你也可以为Tomcat配置一个默认的正则表达式,用来匹配内部信任的代理。默认情况下,IP地址10/8192.168/16169.254/16127/8是被信任的。通过设置server.tomcat.internal-proxies属性可以自定义,比如:

server.tomcat.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}

只有在使用配置文件时才需要双反斜线,如果使用YAML,只需要单个反斜线,比如192\.168\.\d{1,3}\.\d{1,3}

internal-proxies设置为空表示信任所有代理,不要在生产环境使用。

你可以完全控制Tomcat的RemoteIpValve配置,只要关掉自动配置(比如设置server.use-forward-headers=false)并在TomcatEmbeddedServletContainerFactory bean添加一个新value实例。

70.8 配置Tomcat

通常你可以遵循Section 69.8, “Discover built-in options for external properties”关于@ConfigurationProperties(这里主要的是ServerProperties)的建议,但也看下EmbeddedServletContainerCustomizer和各种你可以添加的Tomcat-specific的*Customizers

Tomcat APIs相当丰富,一旦获取到TomcatEmbeddedServletContainerFactory,你就能够以多种方式修改它,或更彻底地就是添加你自己的TomcatEmbeddedServletContainerFactory

70.9 启用Tomcat的多连接器

你可以将org.apache.catalina.connector.Connector添加到TomcatEmbeddedServletContainerFactory,这就能够允许多连接器,比如HTTP和HTTPS连接器:

@Bean
public EmbeddedServletContainerFactory servletContainer() {
    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    tomcat.addAdditionalTomcatConnectors(createSslConnector());
    return tomcat;
}

private Connector createSslConnector() {
    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
    try {
        File keystore = new ClassPathResource("keystore").getFile();
        File truststore = new ClassPathResource("keystore").getFile();
        connector.setScheme("https");
        connector.setSecure(true);
        connector.setPort(8443);
        protocol.setSSLEnabled(true);
        protocol.setKeystoreFile(keystore.getAbsolutePath());
        protocol.setKeystorePass("changeit");
        protocol.setTruststoreFile(truststore.getAbsolutePath());
        protocol.setTruststorePass("changeit");
        protocol.setKeyAlias("apitester");
        return connector;
    }
    catch (IOException ex) {
        throw new IllegalStateException("can't access keystore: [" + "keystore"
                + "] or truststore: [" + "keystore" + "]", ex);
    }
}

70.10 使用Tomcat的LegacyCookieProcessor

Spring Boot使用的内嵌Tomcat不能开箱即用的支持Version 0的Cookie格式,你可能会看到以下错误:

java.lang.IllegalArgumentException: An invalid character [32] was present in the Cookie value

可以的话,你需要考虑将代码升级到只存储遵从最新版Cookie定义的值。如果不能改变写入的cookie,你可以配置Tomcat使用LegacyCookieProcessor。通过向EmbeddedServletContainerCustomizer bean添加一个TomcatContextCustomizer可以开启LegacyCookieProcessor

@Bean
public EmbeddedServletContainerCustomizer cookieProcessorCustomizer() {
    return new EmbeddedServletContainerCustomizer() {

        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                ((TomcatEmbeddedServletContainerFactory) container)
                        .addContextCustomizers(new TomcatContextCustomizer() {

                    @Override
                    public void customize(Context context) {
                        context.setCookieProcessor(new LegacyCookieProcessor());
                    }

                });
            }
        }

    };
}

70.11 使用Jetty替代Tomcat

Spring Boot starters(特别是spring-boot-starter-web)默认都使用Tomcat作为内嵌容器。想使用Jetty替代Tomcat,你需要排除那些Tomcat的依赖并包含Jetty的依赖。为了简化这种事情的处理,Spring Boot将Tomcat和Jetty的依赖捆绑在一起,然后提供了单独的starters。

Maven示例:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

Gradle示例:

configurations {
    compile.exclude module: "spring-boot-starter-tomcat"
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:1.4.1.RELEASE")
    compile("org.springframework.boot:spring-boot-starter-jetty:1.4.1.RELEASE")
    // ...
}

70.12 配置Jetty

通常你可以遵循Section 69.8, “Discover built-in options for external properties”关于@ConfigurationProperties(此处主要是ServerProperties)的建议,但也要看下EmbeddedServletContainerCustomizer

Jetty API相当丰富,一旦获取到JettyEmbeddedServletContainerFactory,你就可以使用很多方式修改它,或更彻底地就是添加你自己的JettyEmbeddedServletContainerFactory

70.13 使用Undertow替代Tomcat

使用Undertow替代Tomcat和使用Jetty替代Tomcat非常类似。你需要排除Tomat依赖,并包含Undertow starter。

Maven示例:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

Gradle示例:

configurations {
    compile.exclude module: "spring-boot-starter-tomcat"
}

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web:1.3.0.BUILD-SNAPSHOT")
    compile 'org.springframework.boot:spring-boot-starter-undertow:1.3.0.BUILD-SNAPSHOT")
    // ...
}

70.14 配置Undertow

通常你可以遵循Section 69.8, “Discover built-in options for external properties”关于@ConfigurationProperties(此处主要是ServerPropertiesServerProperties.Undertow),但也要看下EmbeddedServletContainerCustomizer

一旦获取到UndertowEmbeddedServletContainerFactory,你就可以使用UndertowBuilderCustomizer修改Undertow的配置以满足你的需求,或更彻底地就是添加你自己的UndertowEmbeddedServletContainerFactory

70.15 启用Undertow的多监听器

UndertowBuilderCustomizer添加到UndertowEmbeddedServletContainerFactory,然后使用Builder添加一个listener:

@Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() {
    UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory();
    factory.addBuilderCustomizers(new UndertowBuilderCustomizer() {

        @Override
        public void customize(Builder builder) {
            builder.addHttpListener(8080, "0.0.0.0");
        }

    });
    return factory;
}

70.16 使用Tomcat 7.x或8.0

Spring Boot可以使用Tomcat7&8.0,但默认使用的是Tomcat8.5。如果不能使用Tomcat8.5(例如,因为你使用的是Java1.6),你需要改变classpath去引用一个不同版本。

70.16.1 通过Maven使用Tomcat 7.x或8.0

如果正在使用starters 和parent,你只需要改变Tomcat的version属性,并添加tomcat-juli依赖。比如,对于一个简单的webapp或service:

<properties>
    <tomcat.version>7.0.59</tomcat.version>
</properties>
<dependencies>
    ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-juli</artifactId>
        <version>${tomcat.version}</version>
    </dependency>
    ...
</dependencies>

70.16.2 通过Gradle使用Tomcat7.x或8.0

对于Gradle,你可以通过设置tomcat.version属性改变Tomcat的版本,然后添加tomcat-juli依赖:

ext['tomcat.version'] = '7.0.59'
dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile group:'org.apache.tomcat', name:'tomcat-juli', version:property('tomcat.version')
}

70.17 使用Jetty9.2

Spring Boot可以使用Jetty9.2,但默认使用的是Jetty9.3。如果不能使用Jetty9.3(例如,因为你使用的是Java7),你需要改变classpath去引用Jetty9.2。

70.17.1 通过Maven使用Jetty9.2

如果正在使用starters和parent,你只需添加Jetty starter并覆盖jetty.version属性:

<properties>
    <jetty.version>9.2.17.v20160517</jetty.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

70.17.2 通过Gradle使用Jetty 9.2

对于Gradle,你需要设置jetty.version属性,例如对于一个简单的webapp或service:

ext['jetty.version'] = '9.2.17.v20160517'
dependencies {
    compile ('org.springframework.boot:spring-boot-starter-web') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    compile ('org.springframework.boot:spring-boot-starter-jetty')
}

70.18 使用Jetty 8

Spring Boot支持Jetty 8,但默认使用的是Jetty 9.3。如果不能使用Jetty 9.3(比如因为你使用的是Java 1.6),你需要改变classpath去引用Jetty 8,还需要排除Jetty的WebSocket相关依赖。

70.18.1 通过Maven使用Jetty8

如果正在使用starters和parent,你只需要添加Jetty starter,排除那些需要的WebSocket,并改变version属性。比如,对于一个简单的webapp或service:

<properties>
    <jetty.version>8.1.15.v20140411</jetty.version>
    <jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.eclipse.jetty.websocket</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

70.18.2 通过Gradle使用Jetty8

你可以设置jetty.version属性并排除相关的WebSocket依赖,比如对于一个简单的webapp或service:

ext['jetty.version'] = '8.1.15.v20140411'
dependencies {
    compile ('org.springframework.boot:spring-boot-starter-web') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    compile ('org.springframework.boot:spring-boot-starter-jetty') {
        exclude group: 'org.eclipse.jetty.websocket'
    }
}

70.19 使用@ServerEndpoint创建WebSocket端点

如果想在使用内嵌容器的Spring Boot应用中使用@ServerEndpoint,你需要声明一个单独的ServerEndpointExporter @Bean

@Bean
public ServerEndpointExporter serverEndpointExporter() {
    return new ServerEndpointExporter();
}

该bean将使用底层的WebSocket容器注册任何被@ServerEndpoint注解的beans。当部署到一个单独的servlet容器时,该角色将被一个servlet容器初始化方法执行,ServerEndpointExporter bean也就不需要了。

70.20 启用HTTP响应压缩

Jetty,Tomcat和Undertow支持HTTP响应压缩,你可以通过设置server.compression.enabled启用它:

server.compression.enabled=true

默认情况下,响应信息长度至少2048字节才能触发压缩,通过server.compression.min-response-size属性可以改变该长度。另外,只有响应的content type为以下其中之一时才压缩:

  • text/html
  • text/xml
  • text/plain
  • text/css

你可以通过server.compression.mime-types属性配置。